#!/usr/bin/env python
# -*-coding:utf-8 -*-
# Project_name: KmExportsToExcel
# File_name: km_exports_to_excel_no_window.py
# Author: chen chang song
# Time: 2025年04月13日

import os
import time
import json
from openpyxl import Workbook
from openpyxl.styles import Alignment
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import CellIsRule
from openpyxl.worksheet.datavalidation import DataValidation


class KmToExcelConverter:
    def __init__(self, mind_path):
        self.mind_path = mind_path

    def all_km_path_list(self):
        """主处理函数：遍历目录处理所有km文件
        :return: (原始数据列表, 处理后的数据列表)
        处理流程：
        1. 遍历指定目录下的.km文件
        2. 对每个文件调用extract_km_content解析内容
        3. 将解析结果写入Excel
        """
        file_name_list = []
        km_data_list = []
        try:
            # 遍历目录中的文件
            for filename in os.listdir(self.mind_path):
                if filename.endswith('.km'):
                    file_name_list.append(filename)
                    km_path = os.path.join(self.mind_path, filename)

                    # 解析km文件内容
                    km_data = self.extract_km_content(km_path)
                    km_data_list.append(km_data)

                    # 将数据写入Excel
                    self.test_case_write_to_excel(km_path, km_data)
        except FileNotFoundError:
            print(f"指定的目录 {self.mind_path} 未找到。")

        return km_data_list, km_data_list

    def extract_km_content(self, km_path):
        """解析km文件内容，提取测试用例数据
        :param km_path: km文件路径
        :return: 结构化测试用例数据列表
        数据结构示例：
        [
            ['需求ID', '中', '功能模块1-一级功能1', '验证xxx', '', '步骤', '预期结果'],
            ...
        ]
        """
        with open(km_path, 'r', encoding='utf-8') as f:
            data = f.read()
        data = json.loads(data)
        root = data['root']

        root_text = root['data']['text']
        parts = root_text.split('\n')
        requirement_id = parts[0].split('：')[1].strip()

        result = []
        # 定义合法的resource类型
        allowed_resources = {"用例目录", "用例名称", "用例步骤", "预期结果", "前置条件"}
        errors = []  # 存储错误信息

        def process_node(node, current_path, parent_valid):
            """递归处理节点树的内部函数
            :param node: 当前节点
            :param current_path: 当前功能路径（列表）
            :param parent_valid: 父节点是否合法
            """
            data = node.get('data', {})
            node_text = data.get('text', '')
            resources = data.get('resource', [])
            children = node.get('children', [])

            # 检查节点是否包含合法resource
            current_valid = isinstance(resources, list) and any(r in allowed_resources for r in resources)

            # 如果节点不合法但父节点合法，记录错误
            if not current_valid and parent_valid:
                path = '-'.join(current_path)
                errors.append(f"{km_path}，'{path}' 下一个节点，存在未打标签的数据，请改正后重新转换！")
                return

            # 更新功能路径（仅添加"用例目录"节点）
            new_path = current_path.copy()
            if '用例目录' in resources:
                new_path.append(node_text)

            # 处理测试用例节点
            if '用例名称' in resources:
                path_str = '-'.join(new_path)
                # 确定优先级（1=高，3=低，其他=中）
                priority = data.get('priority')
                priority_str = '中'
                if priority == 1:
                    priority_str = '高'
                elif priority == 3:
                    priority_str = '低'

                # 提取用例步骤和预期结果
                for child in children:
                    pre_condition, step, expected = '', '', ''
                    child_data = child.get('data', {})
                    child_resources = child_data.get('resource', [])

                    # 处理带前置条件的用例
                    if '前置条件' in child_resources:
                        pre_condition = child_data.get('text', '')
                        # 查找步骤和预期结果
                        for step_child in child.get('children', []):
                            step_data = step_child.get('data', {})
                            if '用例步骤' in step_data.get('resource', []):
                                step = step_data.get('text', '')
                                # 查找预期结果
                                for expected_child in step_child.get('children', []):
                                    expected_data = expected_child.get('data', {})
                                    if '预期结果' in expected_data.get('resource', []):
                                        expected = expected_data.get('text', '')
                                        break
                                break
                    # 处理不带前置条件的用例
                    elif '用例步骤' in child_resources:
                        step = child_data.get('text', '')
                        # 查找预期结果
                        for expected_child in child.get('children', []):
                            expected_data = expected_child.get('data', {})
                            if '预期结果' in expected_data.get('resource', []):
                                expected = expected_data.get('text', '')
                                break

                    # 生成完整的测试用例条目
                    if step and expected:
                        result.append([
                            requirement_id,     # 需求ID
                            priority_str,       # 用例等级
                            path_str,           # 功能路径
                            data['text'],       # 用例名称
                            pre_condition,      # 前置条件
                            step,               # 操作步骤
                            expected            # 预期结果
                        ])

            # 递归处理子节点
            for child in children:
                process_node(child, new_path, current_valid)

        # 从根节点开始处理
        for child in root.get('children', []):
            process_node(child, [], False)

        # 打印错误信息
        for error in errors:
            print(f"错误信息:{error}")

        return result

    def test_case_write_to_excel(self, km_path, row_data_list):
        """将测试用例数据写入Excel文件
        :param km_path: 原始km文件路径
        :param row_data_list: 测试用例数据列表
        """
        # ============== 文件准备 ==============
        file_dir, file_name = os.path.split(km_path)
        file_base = os.path.splitext(file_name)[0]


        wb = Workbook()
        ws = wb.active
        ws.title = file_base[:31]  # Excel工作表名称最大31字符

        # ============== 表头设置 ==============
        headers = [
            '需求ID', '用例等级', '用例目录',
            '用例名称', '前置条件', '用例步骤',
            '预期结果', '执行结果', '备注'
        ]
        ws.append(headers)

        # 设置自动筛选（A1到I1列）
        ws.auto_filter.ref = "A1:I1"

        # ============== 列宽设置 ==============
        # 定义每列的宽度（单位：字符）
        col_config = {
            'A': 7,     # 需求ID
            'B': 9,     # 用例等级
            'C': 50,    # 用例目录
            'D': 40,    # 用例名称
            'E': 30,    # 前置条件
            'F': 50,    # 用例步骤
            'G': 30,    # 预期结果
            'H': 12,    # 执行结果
            'I': 25     # 备注
        }
        for col, width in col_config.items():
            ws.column_dimensions[col].width = width

        # ============== 数据验证设置 ==============
        status_options = [
            "通过", "失败", "阻塞",
            "未执行", "待确认",
            "用例待更新", "用例废弃"
        ]
        dv = DataValidation(
            type="list",
            formula1=f'"{",".join(status_options)}"',
            allow_blank=True
        )
        ws.add_data_validation(dv)
        # 应用到H列（执行结果列）
        dv.add(f"H2:H{len(row_data_list) + 1}")

        # ============== 条件格式设置 ==============
        status_colors = {
            "通过": '00FF00',  # 绿色
            "失败": 'FF7F00',  # 橙色
            "阻塞": 'FF0000',  # 红色
            "未执行": 'FFFF00', # 黄色
            "待确认": 'FFFF00',
            "用例待更新": 'FFFF00',
            "用例废弃": 'FFFF00'
        }
        for status, color in status_colors.items():
            rule = CellIsRule(
                operator='equal',
                formula=[f'"{status}"'],
                fill=PatternFill(
                    start_color=color,
                    end_color=color,
                    fill_type='solid'
                )
            )
            ws.conditional_formatting.add(f'H2:H{len(row_data_list) + 1}', rule)

        # ============== 写入测试数据 ==============
        for case_data in row_data_list:
            # 每条数据补充两个空字段（执行结果和备注）
            full_row = case_data + ['', '']
            ws.append(full_row)

        # ============== 格式优化 ==============
        # 冻结首行（方便查看标题）
        ws.freeze_panes = 'A2'

        # 设置自动换行和顶部对齐
        for row in ws.iter_rows():
            for cell in row:
                cell.alignment = Alignment(
                    wrap_text=True,  # 自动换行
                    vertical='top'  # 顶部对齐
                )

        # 设置行高
        ws.row_dimensions[1].height = 20
        for row_idx in range(2, ws.max_row + 1):
            ws.row_dimensions[row_idx].height = 32

        # ============== 文件保存 ==============
        timestamp = time.strftime("%Y%m%d%H%M%S")
        output_path = os.path.join(file_dir, f"{file_base}_{timestamp}.xlsx")
        try:
            wb.save(output_path)
            print(f"成功保存文件: {output_path}")
        except Exception as e:
            print(f"保存文件 {output_path} 时出错: {e}")


if __name__ == "__main__":
    # 使用示例
    mind_path = r'..\file'  # 指定km文件所在目录
    converter = KmToExcelConverter(mind_path)
    raw_data, processed_data = converter.all_km_path_list()